package com.stark.wechat.util;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.ehcache.EhCacheCacheManager;

import com.stark.wechat.context.ApplicationContextUtil;
import com.stark.wechat.context.PropertyUtil;
import com.stark.wechat.context.ScopeUtil;
import com.stark.wechat.domain.Template;
import com.stark.wechat.form.Result;
import com.stark.wechat.message.template.TemplateField;
import com.stark.wechat.message.template.TemplateMessage;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

/**
 * 与系统相关的公共方法集合。
 * @author Ben
 * @since 1.0.0
 * @version 1.0.0, 2017/8/5
 */
public class SysTools {
	private static final int REQUEST_TIME = 1000 * 60;
	
	/**
	 * 获取项目绝对路径，以 {@link java.io.File#separator} 结尾。
	 * @return 项目绝对路径。
	 */
	public static String getAppRealPath() {
		if (ScopeUtil.getRequest() != null) {
			String appRealPath = ScopeUtil.getRequest().getSession().getServletContext().getRealPath("") + File.separator;
			return appRealPath;
		}
		
		String appRealPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
		appRealPath = appRealPath.substring(1, appRealPath.indexOf("WEB-INF"));
		String osName = System.getProperty("os.name").toLowerCase();
		if (osName.contains("mac") || osName.contains("linux")) {
			appRealPath = File.separator + appRealPath;
		}
		return appRealPath;
	}
	
	/**
	 * 获得应用上下文路径，以 "/" 结尾。
	 * @return 应用上下文路径。
	 */
	public static String getContextPath() {
		String contextPath = ScopeUtil.getRequest().getContextPath() + "/";
		return contextPath;
	}
	
	/**
	 * base64 加密字符串。
	 * @param str 字符串。
	 * @return base64 加密后的字符串。
	 * @throws UnsupportedEncodingException 不支持的字符集编码。
	 */
	public static String encodeBase64String(String str) throws UnsupportedEncodingException {
		str = Base64.encodeBase64String(str.getBytes("UTF-8"));
		return str;
	}
	
	/**
	 * 解密 base64 加密的字符串。
	 * @param str base64 加密过的字符串。
	 * @return 解密后的字符串。
	 * @throws UnsupportedEncodingException 不支持的字符集编码。
	 */
	public static String decodeBase64String(String str) throws UnsupportedEncodingException {
		str = new String(Base64.decodeBase64(str), "UTF-8");
		return str;
	}
	
	/**
	 * 从配置文件中取 appId （{@link SysTools#encodeBase64String(String)} 加密）。
	 * @return appID 。
	 * @throws UnsupportedEncodingException 不支持的字符集编码。
	 */
	public static String getAppId() throws UnsupportedEncodingException {
		String appID = PropertyUtil.getProperty("appID");
		appID = SysTools.decodeBase64String(appID);
		return appID;
	}
	
	/**
	 * 从配置文件中取 appsecret （{@link SysTools#encodeBase64String(String)} 加密）。
	 * @return appsecret 。
	 * @throws UnsupportedEncodingException 不支持的字符集编码。
	 */
	public static String getAppsecret() throws UnsupportedEncodingException {
		String appsecret = PropertyUtil.getProperty("appsecret");
		appsecret = SysTools.decodeBase64String(appsecret);
		return appsecret;
	}
	
	/**
	 * 从配置文件中取 token 。
	 * @return token 。
	 */
	public static String getToken() {
		String token = PropertyUtil.getProperty("token");
		return token;
	}
	
	/**
	 * 在网页授权回调域名时，向微信请求 openId 。
	 * @param code 填写 {@link WechatUtil#formatOauth2URL} 获取的 code 参数。
	 * @return {@link Result} 对象。请求成功时 {@link Result#getData()} 中包含属性： (String) access_token 、 (long) expires_in 、 
	 * (String) refresh_token 、 (String) openid 、 (String) scope 。
	 */
	public static Result requestOauth2UserInfo(String code) {
		String appid = null;
		try {
			appid = SysTools.getAppId();
		} catch (UnsupportedEncodingException e) {
			return Result.fail().errmsg("获取 appID 时出错：" + e.getMessage());
		}
		String secret = null;
		try {
			secret = SysTools.getAppsecret();
		} catch (UnsupportedEncodingException e) {
			return Result.fail().errmsg("获取 appsecret 时出错：" + e.getMessage());
		}
		Result result = WechatUtil.requestOauth2UserInfo(appid, secret, code);
		return result;
	}
	
	/**
	 * 从 ehcache 缓存中取值。
	 * @param cacheName 缓存名称。
	 * @param key 键。
	 * @return 缓存中的值。
	 */
	public static Object getValueFromEhcache(String cacheName, Object key) {
		EhCacheCacheManager ehCacheManager = (EhCacheCacheManager) ApplicationContextUtil.getBean("cacheManager");
		CacheManager cacheManager = ehCacheManager.getCacheManager();
		Cache cache = cacheManager.getCache(cacheName);
		
		Element element = cache.get(key);
		Object value = element == null ? null : element.getObjectValue();
		return value;
	}
	
	/**
	 * 把键值对放入 ehcache 缓存。
	 * @param cacheName 缓存名称。
	 * @param key 键。
	 * @param value 值。
	 */
	public static void putIntoEhcache(String cacheName, Object key, Object value) {
		EhCacheCacheManager ehCacheManager = (EhCacheCacheManager) ApplicationContextUtil.getBean("cacheManager");
		CacheManager cacheManager = ehCacheManager.getCacheManager();
		Cache cache = cacheManager.getCache(cacheName);
		
		Element element = new Element(key, value);
		cache.put(element);
	}
	
	/**
	 * 从 ehcache 缓存中删除。
	 * @param cacheName 缓存名称。
	 * @param key 键。
	 * @return 是否成功。
	 */
	public static boolean deleteFromEhcache(String cacheName, Object key) {
		EhCacheCacheManager ehCacheManager = (EhCacheCacheManager) ApplicationContextUtil.getBean("cacheManager");
		CacheManager cacheManager = ehCacheManager.getCacheManager();
		Cache cache = cacheManager.getCache(cacheName);
		
		boolean flag = cache.remove(key);
		return flag;
	}
	
	/**
	 * 从 ehcache 缓存中取值。
	 * <p>默认命名空间为 "publicCache" ，该命名空间下的缓存可能会有自定义的缓存策略定时清空。如有永久保留的需求，请增加命名空间。
	 * @param key 键。
	 * @return 缓存中的值。
	 * @see #getValueFromEhcache(String, Object)
	 */
	public static Object getValueFromEhcache(Object key) {
		Object value = SysTools.getValueFromEhcache("publicCache", key);
		return value;
	}
	
	/**
	 * 把键值对放入 ehcache 缓存。
	 * <p>默认命名空间为 "publicCache" ，该命名空间下的缓存可能会有自定义的缓存策略定时清空。如有永久保留的需求，请增加命名空间。
	 * @param key 键。
	 * @param value 值。
	 * @see #putIntoEhcache(String, Object, Object)
	 */
	public static void putIntoEhcache(Object key, Object value) {
		SysTools.putIntoEhcache("publicCache", key, value);
	}
	
	/**
	 * 从 ehcache 缓存中删除。
	 * <p>默认命名空间为 "publicCache" ，该命名空间下的缓存可能会有自定义的缓存策略定时清空。如有永久保留的需求，请增加命名空间。
	 * @param key 键。
	 * @return 是否成功。
	 * @see #deleteFromEhcache(String, Object)
	 */
	public static boolean deleteFromEhcache(Object key) {
		boolean flag = SysTools.deleteFromEhcache("publicCache", key);
		return flag;
	}
	
	/**
	 * 获取 access_token ，先从缓存中读取，缓存中没有向微信请求。
	 * @return access_token 。
	 * @throws Exception 请求失败时抛出异常。
	 */
	public static String getAccessToken() throws Exception {
		@SuppressWarnings("unchecked")
		Map<String, Object> map = (Map<String, Object>) SysTools.getValueFromEhcache("WECHAT_ACCESS_TOKEN");
		if (map == null || ((Date) map.get("expiration")).compareTo(new Date()) < 0) {	// 未请求过或已过期
			// 请求accessToken
			final String appID = SysTools.getAppId();
			final String appsecret = SysTools.getAppsecret();
			Result result = WechatUtil.requestAccessToken(appID, appsecret);
			if (result.isSuccess()) {
				String accessToken = (String) result.getData().get("access_token");
				Long expiresIn = (Long) result.getData().get("expires_in");
				
				// 计算时效
				Calendar calendar = Calendar.getInstance();
				calendar.add(Calendar.MILLISECOND, expiresIn.intValue() - REQUEST_TIME);
				Date expiration = calendar.getTime();
				
				// 放进缓存
				map = new HashMap<>();
				map.put("accessToken", accessToken);
				map.put("expiration", expiration);
				SysTools.putIntoEhcache("WECHAT_ACCESS_TOKEN", map);
			} else {
				throw new Exception(result.getErrmsg());
			}
		}
		final String accessToken = (String) map.get("accessToken");
		return accessToken;
	}
	
	/**
	 * 获取 jsapi_ticket ，先从缓存中读取，缓存中没有向微信请求。
	 * @return jsapi_ticket 。
	 * @throws Exception 请求失败时抛出异常。
	 */
	public static String getJsapiTicket() throws Exception {
		@SuppressWarnings("unchecked")
		Map<String, Object> map = (Map<String, Object>) SysTools.getValueFromEhcache("WECHAT_JSAPI_TICKET");
		if (map == null || ((Date) map.get("expiration")).compareTo(new Date()) < 0) {	// 未请求过或已过期
			// 请求jsapiTicket
			String accessToken = SysTools.getAccessToken();
			Result result = WechatUtil.requestJsapiTicket(accessToken);
			if (result.isSuccess()) {
				String jsapiTicket = (String) result.getData().get("ticket");
				Long expiresIn = (Long) result.getData().get("expires_in");
				
				// 计算时效
				Calendar calendar = Calendar.getInstance();
				calendar.add(Calendar.MILLISECOND, expiresIn.intValue() - REQUEST_TIME);
				Date expiration = calendar.getTime();
				
				// 放进缓存
				map = new HashMap<>();
				map.put("jsapiTicket", jsapiTicket);
				map.put("expiration", expiration);
				SysTools.putIntoEhcache("WECHAT_JSAPI_TICKET", map);
			} else {
				throw new Exception(result.getErrmsg());
			}
		}
		final String jsapiTicket = (String) map.get("jsapiTicket");
		return jsapiTicket;
	}
	
	/**
	 * 组织模板消息内容。
	 * @param msg 模板消息对象。
	 * @param template 模板对象。
	 * @return 模板消息内容。
	 */
	public static String generateMessageContent(TemplateMessage msg, Template template) {
		String content = template.getContent();
		Map<String, TemplateField> map = msg.getData();
		String key;
		TemplateField field;
		for (Entry<String, TemplateField> entry : map.entrySet()) {
			key = "\\{\\{" + entry.getKey() + ".DATA\\}\\}";
			field = entry.getValue();
			
			content = content.replaceAll(key, field.getValue());
		}
		return content;
	}
	
	/**
	 * 获取应用的访问 URL ，以 "/" 结尾。
	 * @return 应用的访问 URL 。
	 */
	public static String getAppURL() {
		String domainName = PropertyUtil.getProperty("domainName");	// 外网域名
		String contextPath = PropertyUtil.getProperty("contextPath");
		if (StringUtils.isBlank(contextPath)) {
			try {
				contextPath = SysTools.getContextPath();
				contextPath = contextPath.substring(1);
			} catch (Exception e) {
				contextPath = "wechat";
			}
		}
		String appURL = domainName + (domainName.endsWith("/") ? "" : "/") + contextPath + "/";
		return appURL;
	}
	
	/**
	 * 格式化跳转链接。
	 * @param link 跳转链接。
	 * @return 格式化后的跳转链接。
	 * @throws UnsupportedEncodingException 不支持的字符集编码。
	 */
	public static String formatForwardLink(String link) throws UnsupportedEncodingException {
		String appURL = SysTools.getAppURL();
		link = appURL + "access/forward?link=" + SysTools.encodeBase64String(link);
		return link;
	}
	
	/**
	 * 解析跳转链接。
	 * @param link 外部跳转链接。
	 * @return 解析后的跳转链接。
	 * @throws UnsupportedEncodingException 不支持的字符集编码。
	 */
	public static String parseForwardLink(String link) throws UnsupportedEncodingException {
		link = SysTools.decodeBase64String(link);
		return link;
	}
}
